package org.ws.edu.core;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;

import org.apache.log4j.Logger;
import org.ws.edu.model.Answer;
import org.ws.edu.model.Question;
import org.ws.edu.model.Test;
import org.ws.edu.model.Type;

public class TestManagerThread extends Thread {

    private static States CURRENT_STATE = States.MENU_STATE;

    private static Logger logger = Logger.getLogger(TestManagerThread.class);

    private final String folderPath = "./tests";
    private final TestLoader testLoader;

    private ObjectOutputStream out;
    private ObjectInputStream in;

    private Test currentTest;
    private Question currentQuestion;

    private boolean exit = false;
    private int numberOfQuestions;
    private int scalesResult = 0;
    private int commonResult = 0;

    private final String[] help = { "If Test is MultiAnswer type you must respond on question in format \"2, 4\"",
            "If Test is Scales type you must respond on question with one answer\n" };

    public TestManagerThread(Socket socket) {
        try {
            out = new ObjectOutputStream(socket.getOutputStream());
            in = new ObjectInputStream(socket.getInputStream());
        } catch (IOException e) {
            logger.warn("Could not get stream of socket");
        }
        testLoader = TestLoader.getInstance(folderPath);
        logger.info("Instance thread");
    }

    @Override
    public void run() {
        while (!exit) {
            switch (CURRENT_STATE) {
                case MENU_STATE:
                    menuState();
                    break;
                case RESPOND_STATE:
                    respondState();
                    break;
                case RESULT_STATE:
                    resultState();
                    break;
            }
        }
    }

    private void menuState() {
        try {
            switch (in.readInt()) {
                case 0:
                    out.writeObject(getValidTest());
                    out.flush();
                    currentTest = testLoader.getTest((String) in.readObject());
                    CURRENT_STATE = States.RESPOND_STATE;
                    break;
                case 1:
                    out.writeObject(getInvalidTest());
                    out.flush();
                    break;
                case 2:
                    out.writeObject(help);
                    out.flush();
                    break;
                case 3:
                    exit = true;
            }
        } catch (IOException e) {
            logger.warn("Could not write or read to/from socket", e);
            exit = true;
        } catch (ClassNotFoundException e) {
            logger.warn("Could not find String class", e);
            exit = true;
        }
    }

    private void respondState() {

        try {
            out.writeObject(currentTest.getType());
            out.flush();
            List<Question> questions = currentTest.getQuestions();
            numberOfQuestions = questions.size();
            respondOnQuestions(questions);
            if (!questions.isEmpty()) {
                out.writeBoolean(true);
                out.flush();
                if (in.readBoolean()) {
                    respondOnQuestions(questions);
                }
            } else {
                out.writeBoolean(false);
                out.flush();
            }

            CURRENT_STATE = States.RESULT_STATE;
        } catch (IOException e1) {
            logger.warn("Could not write to stream");
            exit = true;
            return;
        }
    }

    private void resultState() {
        if (currentTest.getType().equals(Type.SCALES)) {
            HashMap<Integer, String> results = currentTest.getResult();
            int key = getKeyForResultInMap(results);
            try {
                out.writeObject(results.get(key));
                out.flush();
            } catch (IOException e) {
                logger.warn("Could not write result", e);
                exit = true;
                return;
            }
        } else if (currentTest.getType().equals(Type.SINGLE_ANSWER_TEST) || currentTest.getType().equals(Type.MULTI_ANSWER_TEST)) {
            try {
                out.writeObject(getResultForCommonTests());
                out.flush();
            } catch (IOException e) {
                logger.warn("Could not write result", e);
                exit = true;
                return;
            }
        }
        CURRENT_STATE = States.MENU_STATE;
    }

    private String[] getValidTest() {
        return testLoader.getAvailableTestNames();
    }

    private String[] getInvalidTest() {
        return testLoader.getInvalidTestNames();
    }

    private String getResultForCommonTests() {
        StringBuilder str = new StringBuilder("You was right at ");
        str.append(commonResult);
        str.append(" from ");
        str.append(numberOfQuestions);
        str.append(" questions");
        return str.toString();
    }

    private int getKeyForResultInMap(HashMap<Integer, String> results) {
        Set<Integer> keys = results.keySet();
        Iterator<Integer> iterator = keys.iterator();
        Integer key = null;
        while (iterator.hasNext()) {
            Integer element = iterator.next();
            if (scalesResult < element.intValue()) {
                break;
            } else if (scalesResult > element.intValue()) {
                key = element;
                continue;
            } else if (scalesResult == element.intValue()) {
                key = element;
                break;
            }
        }
        return key;
    }

    private void respondOnQuestions(List<Question> questions) {
        CopyOnWriteArrayList<Question> copyOnWriteQuestions = new CopyOnWriteArrayList<Question>(questions);
        Iterator<Question> iterator = copyOnWriteQuestions.iterator();
        while (iterator.hasNext()) {
            try {
                out.writeBoolean(true);
                out.flush();
                currentQuestion = iterator.next();
                out.writeObject(currentQuestion);
                out.flush();

                List<Answer> userAnswers = (ArrayList<Answer>) in.readObject();
                if (userAnswers.isEmpty()) {
                    continue;
                } else if (currentTest.getType().equals(Type.SINGLE_ANSWER_TEST) || currentTest.getType().equals(Type.MULTI_ANSWER_TEST)) {
                    List<Answer> rightAnswers = currentQuestion.getRightAnswers();
                    CopyOnWriteArrayList<Answer> copyOnWriteAnswers = new CopyOnWriteArrayList<Answer>(userAnswers);
                    Iterator<Answer> answersIterator = copyOnWriteAnswers.iterator();
                    while (answersIterator.hasNext()) {
                        Answer ans = answersIterator.next();
                        if (rightAnswers.contains(ans)) {
                            rightAnswers.remove(ans);
                            userAnswers.remove(ans);
                            continue;
                        } else {
                            break;
                        }

                    }
                    if (rightAnswers.isEmpty() && userAnswers.isEmpty()) {
                        commonResult += 1;
                    }
                    questions.remove(currentQuestion);
                } else if (currentTest.getType().equals(Type.SCALES)) {
                    scalesResult += userAnswers.get(0).getFactor();
                    questions.remove(currentQuestion);

                }

            } catch (IOException e) {
                logger.warn("Could not write to socket", e);
                exit = true;
                return;
            } catch (ClassNotFoundException e) {
                logger.warn("Could not find class", e);
                exit = true;
                return;
            }
        }
        try {
            out.writeBoolean(false);
            out.flush();
        } catch (IOException e) {
            logger.warn("Could not write to socket", e);
            exit = true;
            return;
        }

    }

}

enum States {
    MENU_STATE, RESPOND_STATE, RESULT_STATE
}